using System;
using System.Collections.Generic;
using System.Text;
using System.Net;

namespace AlbumGet
{
    public class Downloader : IDisposable
    {
        #region Private Variables
        private List<DownloadFile> _Files = new List<DownloadFile>();

        private WebClient _downloader = new WebClient();

        private string _DefaultDestinationFolder = "";
        private bool _SkipIfFileExists = true;
        private bool _DeletePartiallyDownloadedFiles = false;
        #endregion

        #region Events
        public delegate void SingleFileEvent(string URL, int FileNumber, int TotalFiles);
        public delegate void MultipleFileEvent(int FileCount);
        public delegate void SingleFileProgressChange(string URL, int PercentFinished);
        public delegate void GenericLog(string Message);

        public event SingleFileEvent SingleFileFinished;
        public event SingleFileEvent SingleFileStarted;
        public event MultipleFileEvent AllFilesFinished;
        public event SingleFileProgressChange SingleFileProcessChanged;
        public event GenericLog LogEvent;
        #endregion

        protected void Log(string Message)
        {
            if (LogEvent != null) LogEvent(Message);
        }

        private bool AllowDownloading = true;

        public Downloader()
        {
            _downloader.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(_downloader_DownloadFileCompleted);
            _downloader.DownloadProgressChanged += new DownloadProgressChangedEventHandler(_downloader_DownloadProgressChanged);
        }

        private void _downloader_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            if (SingleFileProcessChanged != null) SingleFileProcessChanged(e.UserState.ToString(), e.ProgressPercentage);
        }
        public bool DeletePartiallyDownloadedFiles
        {
            get { return _DeletePartiallyDownloadedFiles; }
            set { _DeletePartiallyDownloadedFiles = value; }
        }
        public bool SkipIfFileExists
        {
            get { return _SkipIfFileExists; }
            set { _SkipIfFileExists = value; }
        }

        /// <summary>
        /// Gets or sets the download destination folder
        /// </summary>
        public string DestinationFolder
        {
            get { return _DefaultDestinationFolder; }
            set
            {
                if (!System.IO.Directory.Exists(value))
                    throw new Exception("Destination folder " + value + " doesn't exist.");
                else
                {
                    _DefaultDestinationFolder = value;
                    for (int a = 0; a < this._Files.Count; a++)
                        this._Files[a].DestinationFolder = value;
                }
            }
        }

        /// <summary>
        /// Sets the download URLs
        /// </summary>
        public string[] URLs
        {
            set
            {
                _Files.Clear();
                for (int a = 0; a < value.Length; a++)
                {
                    DownloadFile file = new DownloadFile();
                    file.URL = value[a];
                    file.DestinationFolder = this.DestinationFolder;
                    _Files.Add(file);
                }
            }
        }

        /// <summary>
        /// Gets the current file count
        /// </summary>
        public int FileCount
        {
            get { return _Files.Count; }
        }

        /// <summary>
        /// Gets whether or not a file is currently downloading
        /// </summary>
        public bool CurrentlyDownloadingFile
        {
            get { return _downloader.IsBusy; }
        }

        /// <summary>
        /// Cancels current download, if any.
        /// </summary>
        /// <returns>true: Download was running and has been cancelled; false: No downloads to cancel.</returns>
        public bool CancelCurrentDownload()
        {
            bool res = false;
            if (_downloader.IsBusy)
            {
                res = true;
                _downloader.CancelAsync();
            }
            return res;
        }

        public void CancelBatchDownloads()
        {
            AllowDownloading = false;
        }

        public bool AddFile(string URL)
        {
            DownloadFile file = new DownloadFile();
            file.URL = URL;
            file.DestinationFolder = this.DestinationFolder;
            _Files.Add(file);

            return true;
        }

        private void _downloader_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            int file_number = 0;
            DownloadFile downloaded = null;
            for (int a = 0; a < _Files.Count && downloaded == null; a++)
                if (_Files[a].URL == e.UserState.ToString())
                {
                    file_number = a;
                    downloaded = _Files[a];
                }

            if (!e.Cancelled)
            {
                if (downloaded != null)
                {
                    downloaded.Downloaded = true;

                    if (SingleFileFinished != null) SingleFileFinished(downloaded.URL, file_number + 1, _Files.Count);
                }

                DoDownloading();
            }
            else
            {
                // DELETE partially downloaded files.
                if (_DeletePartiallyDownloadedFiles && downloaded != null)
                {
                    if (downloaded.LocalFileAlreadyExists)
                        System.IO.File.Delete(downloaded.LocalFilename);
                }
            }
        }

        public void StartBatchDownload()
        {
            AllowDownloading = true;
            DoDownloading();
        }

        private void DoDownloading()
        {
            if (AllowDownloading)
            {
                DownloadFile current = GetCurrentFile();
                if (current == null)
                {
                    if (AllFilesFinished != null) AllFilesFinished(_Files.Count);
                }
                else
                {
                    if (current.LocalFileAlreadyExists && this.SkipIfFileExists)
                    {
                        _downloader_DownloadFileCompleted(null, new System.ComponentModel.AsyncCompletedEventArgs(null, false, current.URL));

                        current.Downloaded = true;
                    }
                    else
                    {
                        if (SingleFileStarted != null) SingleFileStarted(current.URL, _Files.IndexOf(current), _Files.Count);
                        current.DownloadStart = DateTime.Now;
                        _downloader.DownloadFileAsync(current.URI, current.LocalFilename, current.URL);
                    }
                }
            }
        }

        public DownloadFile GetCurrentFile()
        {
            DownloadFile file = null;
            for (int a = 0; a < _Files.Count && file == null; a++)
            {
                if (!_Files[a].Downloaded)
                    file = _Files[a];
            }

            return file;
        }

        public class DownloadFile
        {
            public string URL = "";
            public string DestinationFolder = "";
            private bool _Downloaded = false;

            public DateTime DownloadStart = DateTime.Now;
            public DateTime DownloadEnd = DateTime.Now;

            public bool Downloaded
            {
                get { return _Downloaded; }
                set
                {
                    DownloadEnd = DateTime.Now;
                    _Downloaded = value;
                }
            }
            public TimeSpan DownloadTime
            {
                get { return DownloadEnd.Subtract(DownloadStart); }
            }
            public Uri URI
            {
                get { return new Uri(this.URL); }
            }
            public string LocalFilename
            {
                get
                {
                    if (DestinationFolder.Length == 0)
                        throw new Exception("No destination folder set.");

                    string res = "";
                    int index = this.URL.LastIndexOf('/');
                    if (index > 0)
                    {
                        string filename = this.URL.Substring(index + 1);
                        if (DestinationFolder[DestinationFolder.Length - 1] != '/' &&
                            DestinationFolder[DestinationFolder.Length - 1] != '\\')
                        {
                            filename = DestinationFolder + "\\" + filename;
                        }
                        else
                            filename = DestinationFolder + filename;

                        res = filename;
                    }

                    return res;
                }
            }

            public bool LocalFileAlreadyExists
            {
                get { return System.IO.File.Exists(this.LocalFilename); }
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            _Files.Clear();
            _downloader.Dispose();
        }

        #endregion
    }
}
